/*
 * Copyright 2006 - 2013
 *     Stefan Balev     <stefan.balev@graphstream-project.org>
 *     Julien Baudry    <julien.baudry@graphstream-project.org>
 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
 *     Yoann Pigné      <yoann.pigne@graphstream-project.org>
 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
 * 
 * This file is part of GraphStream <http://graphstream-project.org>.
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.stream.file;

import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Stack;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.XMLEvent;

import org.graphstream.stream.SourceBase;

/**
 * GraphML is a comprehensive and easy-to-use file format for graphs. It
 * consists of a language core to describe the structural properties of a graph
 * and a flexible extension mechanism to add application-specific data. Its main
 * features include support of
 * <ul>
 * <li>directed, undirected, and mixed graphs,</li>
 * <li>hypergraphs,</li>
 * <li>hierarchical graphs,</li>
 * <li>graphical representations,</li>
 * <li>references to external data,</li>
 * <li>application-specific attribute data, and</li>
 * <li>light-weight parsers.</li>
 * </ul>
 * 
 * Unlike many other file formats for graphs, GraphML does not use a custom
 * syntax. Instead, it is based on XML and hence ideally suited as a common
 * denominator for all kinds of services generating, archiving, or processing
 * graphs.
 * 
 * <a href="http://graphml.graphdrawing.org/index.html">Source</a>
 */
public class FileSourceGraphML extends SourceBase implements FileSource,
		XMLStreamConstants {

	protected static enum Balise {
		GRAPHML, GRAPH, NODE, EDGE, HYPEREDGE, DESC, DATA, LOCATOR, PORT, KEY, DEFAULT
	}

	protected static enum GraphAttribute {
		ID, EDGEDEFAULT
	}

	protected static enum LocatorAttribute {
		XMLNS_XLINK, XLINK_HREF, XLINK_TYPE
	}

	protected static enum NodeAttribute {
		ID
	}

	protected static enum EdgeAttribute {
		ID, SOURCE, SOURCEPORT, TARGET, TARGETPORT, DIRECTED
	}

	protected static enum DataAttribute {
		KEY, ID
	}

	protected static enum PortAttribute {
		NAME
	}

	protected static enum EndPointAttribute {
		ID, NODE, PORT, TYPE
	}

	protected static enum EndPointType {
		IN, OUT, UNDIR
	}

	protected static enum HyperEdgeAttribute {
		ID
	}

	protected static enum KeyAttribute {
		ID, FOR, ATTR_NAME, ATTR_TYPE
	}

	protected static enum KeyDomain {
		GRAPHML, GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT, ALL
	}

	protected static enum KeyAttrType {
		BOOLEAN, INT, LONG, FLOAT, DOUBLE, STRING
	}

	protected static class Key {
		KeyDomain domain;
		String name;
		KeyAttrType type;
		String def = null;

		Key() {
			domain = KeyDomain.ALL;
			name = null;
			type = KeyAttrType.STRING;
		}

		Object getKeyValue(String value) {
			if (value == null)
				return null;

			switch (type) {
			case STRING:
				return value;
			case INT:
				return Integer.valueOf(value);
			case LONG:
				return Long.valueOf(value);
			case FLOAT:
				return Float.valueOf(value);
			case DOUBLE:
				return Double.valueOf(value);
			case BOOLEAN:
				return Boolean.valueOf(value);
			}

			return value;
		}

		Object getDefaultValue() {
			return getKeyValue(def);
		}
	}

	protected static class Data {
		Key key;
		String id;
		String value;
	}

	protected static class Locator {
		String href;
		String xlink;
		String type;

		Locator() {
			xlink = "http://www.w3.org/TR/2000/PR-xlink-20001220/";
			type = "simple";
			href = null;
		}
	}

	protected static class Port {
		String name;
		String desc;

		LinkedList<Data> datas;
		LinkedList<Port> ports;

		Port() {
			name = null;
			desc = null;

			datas = new LinkedList<Data>();
			ports = new LinkedList<Port>();
		}
	}

	protected static class EndPoint {
		String id;
		String node;
		String port;
		String desc;
		EndPointType type;

		EndPoint() {
			id = null;
			node = null;
			port = null;
			desc = null;
			type = EndPointType.UNDIR;
		}
	}

	protected XMLEventReader reader;
	protected HashMap<String, Key> keys;
	protected LinkedList<Data> datas;
	protected Stack<XMLEvent> events;
	protected Stack<String> graphId;
	protected int graphCounter;

	/**
	 * Build a new source to parse an xml stream in GraphML format.
	 */
	public FileSourceGraphML() {
		events = new Stack<XMLEvent>();
		keys = new HashMap<String, Key>();
		datas = new LinkedList<Data>();
		graphId = new Stack<String>();
		graphCounter = 0;
		sourceId = String.format("<GraphML stream %x>", System.nanoTime());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#readAll(java.lang.String)
	 */
	public void readAll(String fileName) throws IOException {
		readAll(new FileReader(fileName));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#readAll(java.net.URL)
	 */
	public void readAll(URL url) throws IOException {
		readAll(url.openStream());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#readAll(java.io.InputStream)
	 */
	public void readAll(InputStream stream) throws IOException {
		readAll(new InputStreamReader(stream));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#readAll(java.io.Reader)
	 */
	public void readAll(Reader reader) throws IOException {
		begin(reader);
		while (nextEvents())
			;
		end();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#begin(java.lang.String)
	 */
	public void begin(String fileName) throws IOException {
		begin(new FileReader(fileName));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#begin(java.net.URL)
	 */
	public void begin(URL url) throws IOException {
		begin(url.openStream());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#begin(java.io.InputStream)
	 */
	public void begin(InputStream stream) throws IOException {
		begin(new InputStreamReader(stream));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#begin(java.io.Reader)
	 */
	public void begin(Reader reader) throws IOException {
		openStream(reader);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#nextEvents()
	 */
	public boolean nextEvents() throws IOException {
		try {
			__graphml();
		} catch (XMLStreamException ex) {
			throw new IOException(ex);
		}

		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#nextStep()
	 */
	public boolean nextStep() throws IOException {
		return nextEvents();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.file.FileSource#end()
	 */
	public void end() throws IOException {
		closeStream();
	}

	protected XMLEvent getNextEvent() throws IOException, XMLStreamException {
		skipWhiteSpaces();

		if (events.size() > 0)
			return events.pop();

		return reader.nextEvent();
	}

	protected void pushback(XMLEvent e) {
		events.push(e);
	}

	private XMLStreamException newParseError(XMLEvent e, String msg,
			Object... args) {
		return new XMLStreamException(String.format(msg, args), e.getLocation());
	}

	private boolean isEvent(XMLEvent e, int type, String name) {
		boolean valid = e.getEventType() == type;

		if (valid) {
			switch (type) {
			case START_ELEMENT:
				valid = e.asStartElement().getName().getLocalPart()
						.equals(name);
				break;
			case END_ELEMENT:
				valid = e.asEndElement().getName().getLocalPart().equals(name);
				break;
			case ATTRIBUTE:
				valid = ((Attribute) e).getName().getLocalPart().equals(name);
				break;
			case CHARACTERS:
			case NAMESPACE:
			case PROCESSING_INSTRUCTION:
			case COMMENT:
			case START_DOCUMENT:
			case END_DOCUMENT:
			case DTD:
			}
		}

		return valid;
	}

	private void checkValid(XMLEvent e, int type, String name)
			throws XMLStreamException {
		boolean valid = isEvent(e, type, name);

		if (!valid)
			throw newParseError(e, "expecting %s, got %s", gotWhat(type, name),
					gotWhat(e));
	}

	private String gotWhat(XMLEvent e) {
		String v = null;

		switch (e.getEventType()) {
		case START_ELEMENT:
			v = e.asStartElement().getName().getLocalPart();
			break;
		case END_ELEMENT:
			v = e.asEndElement().getName().getLocalPart();
			break;
		case ATTRIBUTE:
			v = ((Attribute) e).getName().getLocalPart();
			break;
		}

		return gotWhat(e.getEventType(), v);
	}

	private String gotWhat(int type, String v) {
		switch (type) {
		case START_ELEMENT:
			return String.format("'<%s>'", v);
		case END_ELEMENT:
			return String.format("'</%s>'", v);
		case ATTRIBUTE:
			return String.format("attribute '%s'", v);
		case NAMESPACE:
			return "namespace";
		case PROCESSING_INSTRUCTION:
			return "processing instruction";
		case COMMENT:
			return "comment";
		case START_DOCUMENT:
			return "document start";
		case END_DOCUMENT:
			return "document end";
		case DTD:
			return "dtd";
		case CHARACTERS:
			return "characters";
		default:
			return "UNKNOWN";
		}
	}

	private Object getValue(Data data) {
		switch (data.key.type) {
		case BOOLEAN:
			return Boolean.parseBoolean(data.value);
		case INT:
			return Integer.parseInt(data.value);
		case LONG:
			return Long.parseLong(data.value);
		case FLOAT:
			return Float.parseFloat(data.value);
		case DOUBLE:
			return Double.parseDouble(data.value);
		case STRING:
			return data.value;
		}

		return data.value;
	}

	private Object getDefaultValue(Key key) {
		switch (key.type) {
		case BOOLEAN:
			return Boolean.TRUE;
		case INT:
			if (key.def != null)
				return Integer.valueOf(key.def);

			return Integer.valueOf(0);
		case LONG:
			if (key.def != null)
				return Long.valueOf(key.def);

			return Long.valueOf(0);
		case FLOAT:
			if (key.def != null)
				return Float.valueOf(key.def);

			return Float.valueOf(0.0f);
		case DOUBLE:
			if (key.def != null)
				return Double.valueOf(key.def);

			return Double.valueOf(0.0);
		case STRING:
			if (key.def != null)
				return key.def;

			return "";
		}

		return key.def != null ? key.def : Boolean.TRUE;
	}

	private void skipWhiteSpaces() throws IOException, XMLStreamException {
		XMLEvent e;

		do {
			if (events.size() > 0)
				e = events.pop();
			else
				e = reader.nextEvent();
		} while (isEvent(e, XMLEvent.CHARACTERS, null)
				&& e.asCharacters().getData().matches("^\\s*$"));

		pushback(e);
	}

	protected void openStream(Reader stream) throws IOException {
		if (reader != null)
			closeStream();

		try {
			XMLEvent e;

			reader = XMLInputFactory.newInstance().createXMLEventReader(stream);

			e = getNextEvent();
			checkValid(e, XMLEvent.START_DOCUMENT, null);

		} catch (XMLStreamException e) {
			throw new IOException(e);
		} catch (FactoryConfigurationError e) {
			throw new IOException(e);
		}
	}

	protected void closeStream() throws IOException {
		try {
			reader.close();
		} catch (XMLStreamException e) {
			throw new IOException(e);
		} finally {
			reader = null;
		}
	}

	protected String toConstantName(Attribute a) {
		return toConstantName(a.getName().getLocalPart());
	}

	protected String toConstantName(String value) {
		return value.toUpperCase().replaceAll("\\W", "_");
	}

	/**
	 * <pre>
	 * <!ELEMENT graphml  ((desc)?,(key)*,((data)|(graph))*)>
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __graphml() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "graphml");

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			pushback(e);
			__desc();

			e = getNextEvent();
		}

		while (isEvent(e, XMLEvent.START_ELEMENT, "key")) {
			pushback(e);
			__key();

			e = getNextEvent();
		}

		while (isEvent(e, XMLEvent.START_ELEMENT, "data")
				|| isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
			pushback(e);

			if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
				__data();
			} else {
				__graph();
			}

			e = getNextEvent();
		}

		checkValid(e, XMLEvent.END_ELEMENT, "graphml");
	}

	private String __characters() throws IOException, XMLStreamException {
		XMLEvent e;
		StringBuilder buffer = new StringBuilder();

		e = getNextEvent();

		while (e.getEventType() == XMLEvent.CHARACTERS) {
			buffer.append(e.asCharacters());
			e = getNextEvent();
		}

		pushback(e);

		return buffer.toString();
	}

	/**
	 * <pre>
	 * <!ELEMENT desc (#PCDATA)>
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private String __desc() throws IOException, XMLStreamException {
		XMLEvent e;
		String desc;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "desc");

		desc = __characters();

		e = getNextEvent();
		checkValid(e, XMLEvent.END_ELEMENT, "desc");

		return desc;
	}

	/**
	 * <pre>
	 * <!ELEMENT locator EMPTY>
	 * <!ATTLIST locator 
	 *           xmlns:xlink   CDATA    #FIXED    "http://www.w3.org/TR/2000/PR-xlink-20001220/"
	 *           xlink:href    CDATA    #REQUIRED
	 *           xlink:type    (simple) #FIXED    "simple"
	 * >
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private Locator __locator() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "locator");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		Locator loc = new Locator();

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				LocatorAttribute attribute = LocatorAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case XMLNS_XLINK:
					loc.xlink = a.getValue();
					break;
				case XLINK_HREF:
					loc.href = a.getValue();
					break;
				case XLINK_TYPE:
					loc.type = a.getValue();
					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid locator attribute '%s'", a
						.getName().getLocalPart());
			}
		}

		e = getNextEvent();
		checkValid(e, XMLEvent.END_ELEMENT, "locator");

		if (loc.href == null)
			throw newParseError(e, "locator requires an href");

		return loc;
	}

	/**
	 * <pre>
	 * <!ELEMENT key (#PCDATA)>
	 * <!ATTLIST key 
	 *           id  ID                                            #REQUIRED
	 *           for (graphml|graph|node|edge|hyperedge|port|endpoint|all) "all"
	 * >
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __key() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "key");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		String id = null;
		KeyDomain domain = KeyDomain.ALL;
		KeyAttrType type = KeyAttrType.STRING;
		String name = null;
		String def = null;

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				KeyAttribute attribute = KeyAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case ID:
					id = a.getValue();

					break;
				case FOR:
					try {
						domain = KeyDomain
								.valueOf(toConstantName(a.getValue()));
					} catch (IllegalArgumentException ex) {
						throw newParseError(e, "invalid key domain '%s'",
								a.getValue());
					}

					break;
				case ATTR_TYPE:
					try {
						type = KeyAttrType
								.valueOf(toConstantName(a.getValue()));
					} catch (IllegalArgumentException ex) {
						throw newParseError(e, "invalid key type '%s'",
								a.getValue());
					}

					break;
				case ATTR_NAME:
					name = a.getValue();

					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid key attribute '%s'", a
						.getName().getLocalPart());
			}
		}

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "default")) {
			def = __characters();

			e = getNextEvent();
			checkValid(e, XMLEvent.END_ELEMENT, "default");

			e = getNextEvent();
		}

		checkValid(e, XMLEvent.END_ELEMENT, "key");

		if (id == null)
			throw newParseError(e, "key requires an id");

		if (name == null)
			name = id;

		System.out.printf("add key \"%s\"\n", id);

		Key k = new Key();
		k.name = name;
		k.domain = domain;
		k.type = type;
		k.def = def;

		keys.put(id, k);
	}

	/**
	 * <pre>
	 * <!ELEMENT port ((desc)?,((data)|(port))*)>
	 * <!ATTLIST port
	 *           name    NMTOKEN  #REQUIRED
	 * >
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private Port __port() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "port");

		Port port = new Port();
		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();
		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				PortAttribute attribute = PortAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case NAME:
					port.name = a.getValue();
					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid attribute '%s' for '<port>'", a
						.getName().getLocalPart());
			}
		}

		if (port.name == null)
			throw newParseError(e,
					"'<port>' element requires a 'name' attribute");

		e = getNextEvent();
		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			pushback(e);
			port.desc = __desc();
		} else {
			while (isEvent(e, XMLEvent.START_ELEMENT, "data")
					|| isEvent(e, XMLEvent.START_ELEMENT, "port")) {
				if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
					Data data;

					pushback(e);
					data = __data();

					port.datas.add(data);
				} else {
					Port portChild;

					pushback(e);
					portChild = __port();

					port.ports.add(portChild);
				}

				e = getNextEvent();
			}
		}

		e = getNextEvent();
		checkValid(e, XMLEvent.END_ELEMENT, "port");

		return port;
	}

	/**
	 * <pre>
	 * <!ELEMENT endpoint ((desc)?)>
	 * <!ATTLIST endpoint 
	 *           id    ID             #IMPLIED
	 *           node  IDREF          #REQUIRED
	 *           port  NMTOKEN        #IMPLIED
	 *           type  (in|out|undir) "undir"
	 * >
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private EndPoint __endpoint() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "endpoint");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();
		EndPoint ep = new EndPoint();

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				EndPointAttribute attribute = EndPointAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case NODE:
					ep.node = a.getValue();
					break;
				case ID:
					ep.id = a.getValue();
					break;
				case PORT:
					ep.port = a.getValue();
					break;
				case TYPE:
					try {
						ep.type = EndPointType.valueOf(toConstantName(a
								.getValue()));
					} catch (IllegalArgumentException ex) {
						throw newParseError(e, "invalid end point type '%s'",
								a.getValue());
					}

					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e,
						"invalid attribute '%s' for '<endpoint>'", a.getName()
								.getLocalPart());
			}
		}

		if (ep.node == null)
			throw newParseError(e,
					"'<endpoint>' element requires a 'node' attribute");

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			pushback(e);
			ep.desc = __desc();
		}

		e = getNextEvent();
		checkValid(e, XMLEvent.END_ELEMENT, "endpoint");

		return ep;
	}

	/**
	 * <pre>
	 * <!ELEMENT data  (#PCDATA)>
	 * <!ATTLIST data 
	 *           key      IDREF        #REQUIRED
	 *           id       ID           #IMPLIED
	 * >
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private Data __data() throws IOException, XMLStreamException {
		XMLEvent e;
		StringBuilder buffer = new StringBuilder();

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "data");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();
		String key = null, id = null;

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				DataAttribute attribute = DataAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case KEY:
					key = a.getValue();
					break;
				case ID:
					id = a.getValue();
					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid attribute '%s' for '<data>'", a
						.getName().getLocalPart());
			}
		}

		if (key == null)
			throw newParseError(e,
					"'<data>' element must have a 'key' attribute");

		e = getNextEvent();

		while (e.getEventType() == XMLEvent.CHARACTERS) {
			buffer.append(e.asCharacters());
			e = getNextEvent();
		}

		checkValid(e, XMLEvent.END_ELEMENT, "data");

		if (keys.containsKey(key))
			newParseError(e, "unknown key '%s'", key);

		Data d = new Data();

		d.key = keys.get(key);
		d.id = id;
		d.value = buffer.toString();

		return d;
	}

	/**
	 * <pre>
	 * <!ELEMENT graph    ((desc)?,((((data)|(node)|(edge)|(hyperedge))*)|(locator)))>
	 * <!ATTLIST graph    
	 *     id          ID                    #IMPLIED
	 *     edgedefault (directed|undirected) #REQUIRED
	 * >
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __graph() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "graph");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		String id = null;
		String desc = null;
		boolean directed = false;
		boolean directedSet = false;

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				GraphAttribute attribute = GraphAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case ID:
					id = a.getValue();
					break;
				case EDGEDEFAULT:
					if (a.getValue().equals("directed"))
						directed = true;
					else if (a.getValue().equals("undirected"))
						directed = false;
					else
						throw newParseError(e,
								"invalid 'edgefault' value '%s'", a.getValue());

					directedSet = true;

					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid node attribute '%s'", a
						.getName().getLocalPart());
			}
		}

		if (!directedSet)
			throw newParseError(e, "graph requires attribute 'edgedefault'");

		String gid = "";

		if (graphId.size() > 0)
			gid = graphId.peek() + ":";

		if (id != null)
			gid += id;
		else
			gid += Integer.toString(graphCounter++);

		graphId.push(gid);

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			pushback(e);
			desc = __desc();

			sendGraphAttributeAdded(sourceId, "desc", desc);

			e = getNextEvent();
		}

		if (isEvent(e, XMLEvent.START_ELEMENT, "locator")) {
			pushback(e);
			__locator();
			// TODO
			e = getNextEvent();
		} else {
			while (isEvent(e, XMLEvent.START_ELEMENT, "data")
					|| isEvent(e, XMLEvent.START_ELEMENT, "node")
					|| isEvent(e, XMLEvent.START_ELEMENT, "edge")
					|| isEvent(e, XMLEvent.START_ELEMENT, "hyperedge")) {
				pushback(e);

				if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
					datas.add(__data());
				} else if (isEvent(e, XMLEvent.START_ELEMENT, "node")) {
					__node();
				} else if (isEvent(e, XMLEvent.START_ELEMENT, "edge")) {
					__edge(directed);
				} else {
					__hyperedge();
				}

				e = getNextEvent();
			}
		}

		graphId.pop();
		checkValid(e, XMLEvent.END_ELEMENT, "graph");
	}

	/**
	 * <pre>
	 * <!ELEMENT node   (desc?,(((data|port)*,graph?)|locator))>
	 * <!ATTLIST node   
	 *     		 id        ID      #REQUIRED
	 * >
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __node() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "node");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		String id = null;
		HashSet<Key> sentAttributes = new HashSet<Key>();

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				NodeAttribute attribute = NodeAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case ID:
					id = a.getValue();
					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid node attribute '%s'", a
						.getName().getLocalPart());
			}
		}

		if (id == null)
			throw newParseError(e, "node requires an id");

		sendNodeAdded(sourceId, id);

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			String desc;

			pushback(e);
			desc = __desc();

			sendNodeAttributeAdded(sourceId, id, "desc", desc);
		} else if (isEvent(e, XMLEvent.START_ELEMENT, "locator")) {
			// TODO
			pushback(e);
			__locator();
		} else {
			while (isEvent(e, XMLEvent.START_ELEMENT, "data")
					|| isEvent(e, XMLEvent.START_ELEMENT, "port")) {
				if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
					Data data;

					pushback(e);
					data = __data();

					sendNodeAttributeAdded(sourceId, id, data.key.name,
							getValue(data));

					sentAttributes.add(data.key);
				} else {
					pushback(e);
					__port();
				}

				e = getNextEvent();
			}
		}

		for (Key k : keys.values()) {
			if ((k.domain == KeyDomain.NODE || k.domain == KeyDomain.ALL)
					&& !sentAttributes.contains(k))
				sendNodeAttributeAdded(sourceId, id, k.name, getDefaultValue(k));
		}

		if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
			Location loc = e.getLocation();

			System.err.printf(
					"[WARNING] %d:%d graph inside node is not implemented",
					loc.getLineNumber(), loc.getColumnNumber());

			pushback(e);
			__graph();

			e = getNextEvent();
		}

		checkValid(e, XMLEvent.END_ELEMENT, "node");
	}

	/**
	 * <pre>
	 * <!ELEMENT edge ((desc)?,(data)*,(graph)?)>
	 * <!ATTLIST edge 
	 *           id         ID           #IMPLIED
	 *           source     IDREF        #REQUIRED
	 *           sourceport NMTOKEN      #IMPLIED
	 *           target     IDREF        #REQUIRED
	 *           targetport NMTOKEN      #IMPLIED
	 *           directed   (true|false) #IMPLIED
	 * >
	 * </pre>
	 * 
	 * @param edgedefault
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __edge(boolean edgedefault) throws IOException,
			XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "edge");

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		HashSet<Key> sentAttributes = new HashSet<Key>();
		String id = null;
		boolean directed = edgedefault;
		String source = null;
		String target = null;

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				EdgeAttribute attribute = EdgeAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case ID:
					id = a.getValue();
					break;
				case DIRECTED:
					directed = Boolean.parseBoolean(a.getValue());
					break;
				case SOURCE:
					source = a.getValue();
					break;
				case TARGET:
					target = a.getValue();
					break;
				case SOURCEPORT:
				case TARGETPORT:
					throw newParseError(e,
							"sourceport and targetport not implemented");
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e, "invalid graph attribute '%s'", a
						.getName().getLocalPart());
			}
		}

		if (id == null)
			throw newParseError(e, "edge must have an id");

		if (source == null || target == null)
			throw newParseError(e, "edge must have a source and a target");

		sendEdgeAdded(sourceId, id, source, target, directed);

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			String desc;

			pushback(e);
			desc = __desc();

			sendEdgeAttributeAdded(sourceId, id, "desc", desc);
		} else {
			while (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
				Data data;

				pushback(e);
				data = __data();

				sendEdgeAttributeAdded(sourceId, id, data.key.name,
						getValue(data));

				sentAttributes.add(data.key);

				e = getNextEvent();
			}
		}

		for (Key k : keys.values()) {
			if ((k.domain == KeyDomain.EDGE || k.domain == KeyDomain.ALL)
					&& !sentAttributes.contains(k))
				sendEdgeAttributeAdded(sourceId, id, k.name, getDefaultValue(k));
		}

		if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
			Location loc = e.getLocation();

			System.err.printf(
					"[WARNING] %d:%d graph inside node is not implemented",
					loc.getLineNumber(), loc.getColumnNumber());

			pushback(e);
			__graph();

			e = getNextEvent();
		}

		checkValid(e, XMLEvent.END_ELEMENT, "edge");
	}

	/**
	 * <pre>
	 * <!ELEMENT hyperedge  ((desc)?,((data)|(endpoint))*,(graph)?)>
	 * <!ATTLIST hyperedge 
	 *           id     ID      #IMPLIED
	 * >
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws XMLStreamException
	 */
	private void __hyperedge() throws IOException, XMLStreamException {
		XMLEvent e;

		e = getNextEvent();
		checkValid(e, XMLEvent.START_ELEMENT, "hyperedge");

		Location loc = e.getLocation();

		System.err.printf(
				"[WARNING] %d:%d hyperedge feature is not implemented",
				loc.getLineNumber(), loc.getColumnNumber());

		String id = null;

		@SuppressWarnings("unchecked")
		Iterator<? extends Attribute> attributes = e.asStartElement()
				.getAttributes();

		while (attributes.hasNext()) {
			Attribute a = attributes.next();

			try {
				HyperEdgeAttribute attribute = HyperEdgeAttribute
						.valueOf(toConstantName(a));

				switch (attribute) {
				case ID:
					id = a.getValue();
					break;
				}
			} catch (IllegalArgumentException ex) {
				throw newParseError(e,
						"invalid attribute '%s' for '<endpoint>'", a.getName()
								.getLocalPart());
			}
		}

		if (id == null)
			throw newParseError(e,
					"'<hyperedge>' element requires a 'node' attribute");

		e = getNextEvent();

		if (isEvent(e, XMLEvent.START_ELEMENT, "desc")) {
			pushback(e);
			__desc();
		} else {
			while (isEvent(e, XMLEvent.START_ELEMENT, "data")
					|| isEvent(e, XMLEvent.START_ELEMENT, "endpoint")) {
				if (isEvent(e, XMLEvent.START_ELEMENT, "data")) {
					pushback(e);
					__data();
				} else {
					pushback(e);
					__endpoint();
				}

				e = getNextEvent();
			}
		}

		if (isEvent(e, XMLEvent.START_ELEMENT, "graph")) {
			loc = e.getLocation();

			System.err.printf(
					"[WARNING] %d:%d graph inside node is not implemented",
					loc.getLineNumber(), loc.getColumnNumber());

			pushback(e);
			__graph();

			e = getNextEvent();
		}

		e = getNextEvent();
		checkValid(e, XMLEvent.END_ELEMENT, "hyperedge");
	}
}
